"""
Project Context Manager

Handles compile_commands.json parsing and project-wide configuration
for accurate C++ code analysis.
"""

import json
import os
from pathlib import Path
from typing import Dict, List, Optional, Set
from dataclasses import dataclass


@dataclass
class CompileCommand:
    """Represents a single compilation command."""
    directory: str
    file: str
    command: str
    arguments: List[str]
    
    @classmethod
    def from_dict(cls, data: Dict) -> 'CompileCommand':
        """Create CompileCommand from dictionary."""
        if 'arguments' in data:
            arguments = data['arguments']
            command = ' '.join(arguments) if arguments else ""
        else:
            command = data.get('command', '')
            arguments = command.split() if command else []
        
        return cls(
            directory=data['directory'],
            file=data['file'],
            command=command,
            arguments=arguments
        )


class ProjectContext:
    """Manages project-wide context and compilation settings."""
    
    def __init__(self, compile_commands_path: Optional[str] = None):
        self.compile_commands_path = compile_commands_path
        self.compile_commands: Dict[str, CompileCommand] = {}
        self.project_root: Optional[str] = None
        
        if compile_commands_path:
            self.load_compile_commands()
    
    def load_compile_commands(self):
        """Load compile_commands.json file."""
        if not self.compile_commands_path:
            return
        
        compile_commands_path = Path(self.compile_commands_path)
        if not compile_commands_path.exists():
            raise FileNotFoundError(f"compile_commands.json not found: {self.compile_commands_path}")
        
        with open(compile_commands_path, 'r') as f:
            commands_data = json.load(f)
        
        self.project_root = str(compile_commands_path.parent)
        
        for cmd_data in commands_data:
            compile_cmd = CompileCommand.from_dict(cmd_data)
            # Normalize file path for lookup
            file_path = Path(compile_cmd.file)
            if not file_path.is_absolute():
                file_path = Path(compile_cmd.directory) / file_path
            
            self.compile_commands[str(file_path.resolve())] = compile_cmd
    
    def get_compile_args_for_file(self, file_path: str) -> List[str]:
        """Get compilation arguments for a specific file."""
        file_path = str(Path(file_path).resolve())
        
        if file_path in self.compile_commands:
            cmd = self.compile_commands[file_path]
            return self._extract_clang_args(cmd.arguments)
        
        # Fallback: try to find a similar file or use default args
        return self._get_default_args()
    
    def _extract_clang_args(self, arguments: List[str]) -> List[str]:
        """Extract relevant arguments for clang analysis."""
        clang_args = []
        skip_next = False
        
        for i, arg in enumerate(arguments):
            if skip_next:
                skip_next = False
                continue
            
            # Include paths
            if arg.startswith('-I'):
                if arg == '-I':
                    # Next argument is the include path
                    if i + 1 < len(arguments):
                        clang_args.extend(['-I', arguments[i + 1]])
                        skip_next = True
                else:
                    clang_args.append(arg)
            
            # Macro definitions
            elif arg.startswith('-D'):
                clang_args.append(arg)
            
            # Language standard
            elif arg.startswith('-std='):
                clang_args.append(arg)
            
            # System include paths
            elif arg in ['-isystem', '-iquote', '-idirafter']:
                if i + 1 < len(arguments):
                    clang_args.extend([arg, arguments[i + 1]])
                    skip_next = True
            
            # Warning flags (might affect parsing)
            elif arg.startswith('-W'):
                clang_args.append(arg)
            
            # Optimization flags (usually not needed for analysis)
            elif arg.startswith('-O'):
                continue
            
            # Exclude output-related flags
            elif arg in ['-c', '-o'] or arg.startswith('-o'):
                if arg == '-o' or arg == '-c':
                    skip_next = True
                continue
        
        return clang_args
    
    def _get_default_args(self) -> List[str]:
        """Get default compilation arguments when no specific command is found."""
        return [
            '-std=c++17',  # Default to C++17
            '-I/usr/include',
            '-I/usr/local/include'
        ]
    
    def find_compile_commands_file(self, start_path: str) -> Optional[str]:
        """Find compile_commands.json file by walking up the directory tree."""
        current_path = Path(start_path).resolve()
        
        # If start_path is a file, start from its directory
        if current_path.is_file():
            current_path = current_path.parent
        
        # Walk up the directory tree
        while current_path != current_path.parent:  # Not at root
            compile_commands_file = current_path / 'compile_commands.json'
            if compile_commands_file.exists():
                return str(compile_commands_file)
            current_path = current_path.parent
        
        return None
    
    def auto_detect_and_load(self, source_file_path: str):
        """Auto-detect and load compile_commands.json from source file location."""
        compile_commands_file = self.find_compile_commands_file(source_file_path)
        if compile_commands_file:
            self.compile_commands_path = compile_commands_file
            self.load_compile_commands()
    
    def get_include_paths(self, file_path: str) -> List[str]:
        """Extract include paths for a file."""
        args = self.get_compile_args_for_file(file_path)
        include_paths = []
        
        i = 0
        while i < len(args):
            if args[i] == '-I' and i + 1 < len(args):
                include_paths.append(args[i + 1])
                i += 2
            elif args[i].startswith('-I'):
                include_paths.append(args[i][2:])
                i += 1
            else:
                i += 1
        
        return include_paths
    
    def get_macro_definitions(self, file_path: str) -> Dict[str, str]:
        """Extract macro definitions for a file."""
        args = self.get_compile_args_for_file(file_path)
        macros = {}
        
        for arg in args:
            if arg.startswith('-D'):
                macro_def = arg[2:]
                if '=' in macro_def:
                    name, value = macro_def.split('=', 1)
                    macros[name] = value
                else:
                    macros[macro_def] = '1'
        
        return macros